package com.lagou.edu.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * ip防暴刷：限制单个客户端IP在最近X分钟内请求注册接⼝不能超过Y次
 */
@Component
@Slf4j
@RefreshScope
public class IPFilter implements GlobalFilter, Ordered {
    @Value("${durationMinutes}")
    private int durationMinutes;

    @Value("${maxCallTimes}")
    private int maxCallTimes;

    private Map<String, List<Long>> ipCallTimesMap = new ConcurrentHashMap<>();

    // 为防止oom，需要定时清理过期数据
    @PostConstruct
    private void startCleanCacheDataTimer() {
        log.info("durationMinutes={}, maxCallTimes={}", durationMinutes, maxCallTimes);
        ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        executorService.scheduleAtFixedRate(() -> {
            Collection<List<Long>> lists = ipCallTimesMap.values();
            for (List<Long> list : lists) {
                Iterator<Long> iterator = list.iterator();
                while (iterator.hasNext()) {
                    Long callTime = iterator.next();
                    long boundary = System.currentTimeMillis() - durationMinutes * 60 * 1000;
                    if (callTime < boundary) {
                        iterator.remove();
                    }
                }
            }
        }, 5, 5, TimeUnit.SECONDS);
    }

    /**
     * 过滤器核⼼⽅法
     *
     * @param exchange 封装了request和response对象的上下⽂
     * @param chain    ⽹关过滤器链（包含全局过滤器和单路由过滤器）
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String ip = request.getRemoteAddress().getHostString();

        if (!Pattern.matches("/api/user/register/\\S*", request.getURI().getPath())) {
            return chain.filter(exchange);
        }

        // 拦截注册请求，按ip统计过去注册次数
        if (!ipCallTimesMap.containsKey(ip)) {
            ipCallTimesMap.put(ip, Collections.synchronizedList(new ArrayList<>()));
        }

        List<Long> times = ipCallTimesMap.get(ip);
        long boundary = System.currentTimeMillis() - durationMinutes * 60 * 1000;
        long count = times.stream().filter(t -> t >= boundary).count();
        log.info("过去{}分钟ip[{}]的注册请求次数为{}", durationMinutes, ip, count);

        // 超过阈值直接返回错误码及错误信息（错误码：303，错误信息：您频繁进⾏注册，请求已被拒绝）
        if (count >= maxCallTimes) {
            log.info("过去{}分钟的注册请求次数已达到阈值{}", durationMinutes, maxCallTimes);
            response.setStatusCode(HttpStatus.SEE_OTHER);
            String data = "您频繁进行注册，请求已被拒绝";
            DataBuffer wrap = response.bufferFactory().wrap(data.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(wrap));
        }

        times.add(System.currentTimeMillis());
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
